#include "src/net/socket.h"

#include <netinet/in.h>
#include <netinet/tcp.h>

#include <format>

#include "src/net/inet_address.h"
#include "src/net/sockets_opt.h"

namespace fio66::net {
Socket::Socket(const int sock_fd) : sock_fd_(sock_fd) {}

Socket::~Socket() { sockets::Close(sock_fd_); }

int Socket::Fd() const { return sock_fd_; }

bool Socket::GetTcpInfo(struct tcp_info* tcp_info) const {
  socklen_t len = sizeof(*tcp_info);
  ZeroMemory(*tcp_info);
  return ::getsockopt(sock_fd_, SOL_TCP, TCP_INFO, tcp_info, &len) == 0;
}

bool Socket::GetTcpInfoString(char* buf, const int len) const {
  struct tcp_info tcpi;
  bool ok = GetTcpInfo(&tcpi);
  if (ok) {
    snprintf(
        buf, len,
        "unrecovered=%u "
        "rto=%u ato=%u snd_mss=%u rcv_mss=%u "
        "lost=%u retrans=%u rtt=%u rttvar=%u "
        "sshthresh=%u cwnd=%u total_retrans=%u",
        tcpi.tcpi_retransmits,  // Number of unrecovered [RTO] timeouts
        tcpi.tcpi_rto,          // Retransmit timeout in usec
        tcpi.tcpi_ato,          // Predicted tick of soft clock in usec
        tcpi.tcpi_snd_mss, tcpi.tcpi_rcv_mss,
        tcpi.tcpi_lost,     // Lost packets
        tcpi.tcpi_retrans,  // Retransmitted packets out
        tcpi.tcpi_rtt,      // Smoothed round trip time in usec
        tcpi.tcpi_rttvar,   // Medium deviation
        tcpi.tcpi_snd_ssthresh, tcpi.tcpi_snd_cwnd,
        tcpi.tcpi_total_retrans);  // Total retransmits for entire connection
  }
  return ok;
}

std::optional<std::string> Socket::GetTcpInfoString() const {
  struct tcp_info tcpi;
  if (GetTcpInfo(&tcpi)) {
    return std::format(
        "unrecovered={} "
        "rto={} ato={} snd_mss={} rcv_mss={} "
        "lost={} retrans={} rtt={} rttvar={} "
        "sshthresh={} cwnd={} total_retrans={}",
        tcpi.tcpi_retransmits,  // Number of unrecovered [RTO] timeouts
        tcpi.tcpi_rto,          // Retransmit timeout in usec
        tcpi.tcpi_ato,          // Predicted tick of soft clock in usec
        tcpi.tcpi_snd_mss, tcpi.tcpi_rcv_mss,
        tcpi.tcpi_lost,     // Lost packets
        tcpi.tcpi_retrans,  // Retransmitted packets out
        tcpi.tcpi_rtt,      // Smoothed round trip time in usec
        tcpi.tcpi_rttvar,   // Medium deviation
        tcpi.tcpi_snd_ssthresh, tcpi.tcpi_snd_cwnd, tcpi.tcpi_total_retrans);
  }
  return "";
}

void Socket::BindAddress(const InetAddress& local_addr) {
  sockets::BindOrDie(sock_fd_, local_addr.GetSockAddr());
}

void Socket::Listen() { sockets::ListenOrDie(sock_fd_); }

int Socket::Accept(InetAddress* peer_addr) {
  struct sockaddr_in6 addr;
  ZeroMemory(addr);
  int connfd = sockets::Accept(sock_fd_, &addr);
  if (connfd >= 0) {
    peer_addr->SetSockAddrInet6(addr);
  }
  return connfd;
}

void Socket::ShutDownWrite() { sockets::ShutdownWrite(sock_fd_); }

void Socket::SetTcpNoDelay(const bool on) {
  int optval = on ? 1 : 0;
  ::setsockopt(sock_fd_, IPPROTO_TCP, TCP_NODELAY, &optval,
               static_cast<socklen_t>(sizeof optval));
}

void Socket::SetReuseAddr(const bool on) {
  int optval = on ? 1 : 0;
  ::setsockopt(sock_fd_, SOL_SOCKET, SO_REUSEADDR, &optval,
               static_cast<socklen_t>(sizeof optval));
}

void Socket::SetReusePort(const bool on) {
#ifdef SO_REUSEPORT
  int optval = on ? 1 : 0;
  int ret = ::setsockopt(sock_fd_, SOL_SOCKET, SO_REUSEPORT, &optval,
                         static_cast<socklen_t>(sizeof optval));
  if (ret < 0 && on) {
    LOGE << "SO_REUSEPORT failed.";
  }
#else
  if (on) {
    LOGE << "SO_REUSEPORT is not supported.";
  }
#endif
}

void Socket::SetKeepAlive(const bool on) {
  int optval = on ? 1 : 0;
  ::setsockopt(sock_fd_, SOL_SOCKET, SO_KEEPALIVE, &optval,
               static_cast<socklen_t>(sizeof optval));
}
}  // namespace fio66::net
